﻿// OneScript: ./OInt/core/Modules/OPI_Notion.os
// Lib: Notion
// CLI: notion

// MIT License

// Copyright (c) 2023 Anton Tsitavets

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

// https://github.com/Bayselonarrend/OpenIntegrations

// BSLLS:LatinAndCyrillicSymbolInWord-off
// BSLLS:IncorrectLineBreak-off
// BSLLS:NumberOfOptionalParams-off
// BSLLS:UsingServiceTag-off

//@skip-check method-too-many-params
//@skip-check module-structure-top-region
//@skip-check module-structure-method-in-regions
//@skip-check wrong-string-literal-content

// Uncomment if OneScript is executed
#Use "../../tools"

#Region Public

#Region PageManagement

// Create page
// Creates a child page above another parent page
// 
// Parameters:
// Token - String - Token - token
// Parent - String - Parent ID - page
// Title - String - Page title - title
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function CreatePage(Val Token, Val Parent, Val Title) Export
    
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(Parent);
    OPI_TypeConversion.GetLine(Title);
    
    Headers = CreateRequestHeaders(Token);   
    Properties = New Structure;
    Parameters = New Structure;
    
    AddPageHeader(Title, Properties);
    AddPageParent(Parent, False, Parameters);
    
    Parameters.Insert("properties", Properties);
    
    Response = OPI_Tools.Post("https://api.notion.com/v1/pages", Parameters, Headers);
    
    Return Response;
    
EndFunction

// Create page in database
// Creates a page in the parent database
// 
// Parameters:
// Token - String - Token - token 
// Parent - String - Parent database ID - base
// Data - Map Of KeyAndValue - Properties map - data
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function CreatePageInDatabase(Val Token, Val Parent, Val Data) Export
    
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(Parent);
    OPI_TypeConversion.GetCollection(Data);
    
    Headers = CreateRequestHeaders(Token);
    Parameters = New Structure;
    
    AddPageParent(Parent, True, Parameters);

    Properties = FillDataBySchema(Parent, Data, Token);    
    Parameters.Insert("properties", Properties);
    
    Response = OPI_Tools.Post("https://api.notion.com/v1/pages", Parameters, Headers);
    
    Return Response;
    
EndFunction

// Get page
// Gets information about the page by ID
// 
// Parameters:
// Token - String - Token - token
// Page - String - Page ID - page 
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function GetPage(Val Token, Val Page) Export
    
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(Page);
    
    Headers = CreateRequestHeaders(Token);
    ConvertID(Page);  
    
    Response = OPI_Tools.Get("https://api.notion.com/v1/pages/" + Page, , Headers);
    
    Return Response;
    
EndFunction

// Edit page properties
// Changes the properties of an existing page
// 
// Parameters:
// Token - String - Token - token
// Page - String - ID of the page being modified - page
// Data - Map Of KeyAndValue - Map of editable parameters - data
// Icon - String - URL of the page icon image - icon
// Cover - String - URL of the page cover image - cover
// Archived - Boolean - Archive page or not (boolean) - archive
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function EditPageProperties(Val Token
    , Val Page
    , Val Data = ""
    , Val Icon = ""
    , Val Cover = ""
    , Val Archived = False) Export
    
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(Page);
    OPI_TypeConversion.GetLine(Icon);
    OPI_TypeConversion.GetLine(Cover);
    OPI_TypeConversion.GetBoolean(Archived);
    OPI_TypeConversion.GetCollection(Data);
    
    Headers = CreateRequestHeaders(Token);
    Parameters = New Structure;
    Files = "files";
    
    If ValueIsFilled(Data) 
        And (TypeOf(Data) = Type("Map") Or TypeOf(Data) = Type("Structure")) Then
        Properties = FillDataBySchema(Page, Data, Token, False); 
    Else 
        Properties = New Map;
    EndIf;
        
    If ValueIsFilled(Icon) Then
        IconMap = New Map;
        IconMap.Insert("Icon", Icon);
        
        IconObject = ConvertValueByType(Files, IconMap);
        IconObject = IconObject[Files][0];
        IconObject.Delete("name");
        
        Parameters.Insert("icon", IconObject);
    EndIf;
    
    If ValueIsFilled(Cover) Then
        CoverMap = New Map;
        CoverMap.Insert("Cover", Cover);

        CoverObject = ConvertValueByType(Files, CoverMap);
        CoverObject = CoverObject[Files][0];
        CoverObject.Delete("name");

        Parameters.Insert("cover", CoverObject);
    EndIf;

    Parameters.Insert("properties", Properties);
    Parameters.Insert("archived" , Archived);
    
    ConvertID(Page);

    Response = OPI_Tools.Patch("https://api.notion.com/v1/pages/" + Page, Parameters, Headers);
    
    Return Response;
    
EndFunction

#EndRegion

#Region DatabaseManagement

// Create database
// Creates a database
// 
// Parameters:
// Token - String - Token - token 
// Parent - String - Parent page ID - page
// Title - String - Database title - title
// Properties - Structure Of String - Database properties - props
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function CreateDatabase(Val Token, Val Parent, Val Title, Val Properties = "") Export

    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(Parent);
    OPI_TypeConversion.GetLine(Title);
    OPI_TypeConversion.GetCollection(Properties);
    
    // Example structure/property map
    
    // Name : title
    // Description : rich_text
    // InProgress : checkbox 
    // Count    : number
    // Date : date
    // Status : Map
    // Active : green
    // Inactive    : red
    // Archive : yellow
    
    // All pages created as children must have parent base properties

    If Not TypeOf(Properties) = Type("Structure") And Not TypeOf(Properties) = Type("Map") Then
        Properties = New Structure("Name", "title");
    EndIf;

    Headers = CreateRequestHeaders(Token);     
    Parameters = New Structure;
    
    AddDatabaseParent(Parent, False, Parameters);
    AddDatabaseHeader(Title, Parameters);
    AddDatabaseProperties(Properties, Parameters);

    Response = OPI_Tools.Post("https://api.notion.com/v1/databases", Parameters, Headers);
    
    Return Response;
    
EndFunction

// Get database
// Get database information
// 
// Parameters:
// Token - String - Token - token
// Base - String - Database ID - base
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function GetDatabase(Val Token, Val Base) Export
   
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(Base);
    
    Headers = CreateRequestHeaders(Token);
    ConvertID(Base);  
    
    Response = OPI_Tools.Get("https://api.notion.com/v1/databases/" + Base, , Headers);
    
    Return Response; 
    
EndFunction

// Edit database properties
// Edits properties of an existing database
// 
// Parameters:
// Token - String - Token - token
// Base - String - Target database ID - base
// Properties - Map of KeyAndValue - New or modified database properties - props
// Title - String - New database title - title
// Description - String - New database description - description
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function EditDatabaseProperties(Val Token, Val Base, Val Properties = "", Val Title = "", Val Description = "") Export
    
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(Base);
    OPI_TypeConversion.GetLine(Title);
    OPI_TypeConversion.GetLine(Description);
    OPI_TypeConversion.GetCollection(Properties);
    
    Parameters = New Structure;
    Headers = CreateRequestHeaders(Token);  
    ConvertID(Base); 
    
    If ValueIsFilled(Title) Then
        AddDatabaseHeader(Title, Parameters);
    EndIf;
    
    If ValueIsFilled(Description) Then
        AddDatabaseDescription(Description, Parameters);
    EndIf;
    
    If TypeOf(Properties) = Type("Structure") Or TypeOf(Properties) = Type("Map") Then
        AddDatabaseProperties(Properties, Parameters);
    EndIf;
    
    Response = OPI_Tools.Patch("https://api.notion.com/v1/databases/" + Base, Parameters, Headers);
    
    Return Response;
    
EndFunction

#EndRegion 

#Region BlocksManagment

// Create block
// Creates a new block based on an existing block
// 
// Parameters:
// Token - String - Token - token
// Parent - String - Parent block or page ID - page
// Block - String, Map Of KeyAndValue - Block ID or block sample itself - block
// InsertAfter - String - Block ID after which to insert the new one - prev
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function CreateBlock(Val Token, Val Parent, Val Block, Val InsertAfter = "") Export
  
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(Parent);
    OPI_TypeConversion.GetLine(InsertAfter);
    OPI_TypeConversion.GetCollection(Block);
    
    If TypeOf(Block) = Type("Array") Then
        Block = Block[0];
    EndIf;
    
    Headers = CreateRequestHeaders(Token); 
    ConvertID(Parent);
    
    If TypeOf(Block) = Type("String") Then
        ConvertID(Block);
        Block = ReturnBlock(Token, Block);
    EndIf;
    
    BlockArray = New Array;
    BlockArray.Add(Block);
    
    Parameters = New Map;
    Parameters.Insert("children", BlockArray);
    
    If ValueIsFilled(InsertAfter) Then
        Parameters.Insert("after", InsertAfter);
    EndIf;
    
    URL = "https://api.notion.com/v1/blocks/" + Parent + "/children";
    Response = OPI_Tools.Patch(URL, Parameters, Headers);
    
    Return Response;
       
EndFunction

// Return block
// Returns the block structure by ID
// 
// Parameters:
// Token - String - Token - token
// BlockID - String - Block ID - block
// OnlyBase - Boolean - True > service fields are deleted, only the block itself remains - core
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function ReturnBlock(Val Token, Val BlockID, Val OnlyBase = True) Export
    
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(BlockID);
    OPI_TypeConversion.GetBoolean(OnlyBase);
    
    ConvertID(BlockID);
    
    Headers = CreateRequestHeaders(Token);
    Response = OPI_Tools.Get("https://api.notion.com/v1/blocks/" + BlockID, , Headers);
    
    If OnlyBase Then
        RemoveExtraBlockFields(Response);    
    EndIf;
    
    Return Response;
        
EndFunction

// Return child blocks
// Returns list of child blocks of parent block
// 
// Parameters:
// Token - String - Token - token
// BlockID - String - Parent block ID - block
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function ReturnChildBlocks(Val Token, Val BlockID) Export
    
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(BlockID);
    
    ConvertID(BlockID);
    
    Headers = CreateRequestHeaders(Token);
    Response = OPI_Tools.Get("https://api.notion.com/v1/blocks/" + BlockID + "/children", , Headers);
    
    Return Response;
    
EndFunction

// Delete block
// Deletes block by ID
// 
// Parameters:
// Token - String - Token - token
// BlockID - String - Block ID - block
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function DeleteBlock(Val Token, Val BlockID) Export
    
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(BlockID);
    
    ConvertID(BlockID);
    
    Headers = CreateRequestHeaders(Token);
    Response = OPI_Tools.Delete("https://api.notion.com/v1/blocks/" + BlockID, , Headers);
    
    Return Response;
        
EndFunction
    
#EndRegion

#Region Users

// User list
// Returns a list of workspace users
// 
// Parameters:
// Token - String - Token - token
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function UserList(Val Token) Export

    OPI_TypeConversion.GetLine(Token);
    
    Headers = CreateRequestHeaders(Token);
    Response = OPI_Tools.Get("https://api.notion.com/v1/users", , Headers);
    
    Return Response;
        
EndFunction

// Get user data
// Gets user data by ID
// 
// Parameters:
// Token - String - Token - token
// UserID - String - Target user ID - user
// 
// Returns:
// Map Of KeyAndValue - Serialized JSON response from Notion
Function GetUserData(Val Token, Val UserID) Export
    
    OPI_TypeConversion.GetLine(Token);
    OPI_TypeConversion.GetLine(UserID);
    
    ConvertID(UserID);
    
    Headers = CreateRequestHeaders(Token);
    Response = OPI_Tools.Get("https://api.notion.com/v1/users/" + UserID, , Headers);
    
    Return Response;
    
EndFunction

#EndRegion

#EndRegion

#Region Private

Function CreateRequestHeaders(Val Token)
    
    OPI_TypeConversion.GetLine(Token);
    
    Headers = New Map;
    Headers.Insert("Authorization" , "Bearer " + Token);
    Headers.Insert("Notion-Version", "2022-06-28");
    
    Return Headers;
        
EndFunction

Procedure ConvertID(Identifier)
    
    OPI_TypeConversion.GetLine(Identifier);
    
    Identifier = StrReplace(Identifier, "-", "");
    
EndProcedure

Procedure AddPageParent(Val Parent, Val ParentBase, MainStructure)
  
    OPI_TypeConversion.GetLine(ParentBase);
    
    ConvertID(Parent);
  
    IdentifierField = ?(ParentBase, "database_id", "page_id");
    ParentStructure = New Structure(IdentifierField, Parent);

    MainStructure.Insert("parent", ParentStructure);
     
EndProcedure

Procedure AddDatabaseParent(Val Parent, Val ParentBase, MainStructure)
  
    OPI_TypeConversion.GetLine(ParentBase);
    
    ConvertID(Parent);
  
    IdentifierField = ?(ParentBase, "database_id", "page_id");
    
    ParentStructure = New Structure();
    ParentStructure.Insert("type" , IdentifierField);
    ParentStructure.Insert(IdentifierField, Parent);

    MainStructure.Insert("parent", ParentStructure);
     
EndProcedure

Procedure AddPageHeader(Val Title, MainStructure) 
    
    OPI_TypeConversion.GetLine(Title);
    
    SubordinateStructure = New Structure;
    DataStructure = New Structure;
    TextStructure = New Structure;
    DataArray = New Array;
    Title = "title";
    
    TextStructure.Insert("content", Title);
    TextStructure.Insert("link" , Undefined);
   
    DataStructure.Insert("text", TextStructure);
    DataStructure.Insert("type", "text");
    
    DataArray.Add(DataStructure);
    
    SubordinateStructure.Insert("id" , Title);
    SubordinateStructure.Insert("type" , Title);
    SubordinateStructure.Insert(Title , DataArray);
    
    MainStructure.Insert(Title, SubordinateStructure);
    
EndProcedure

Procedure AddDatabaseHeader(Val Title, MainStructure)
    
    OPI_TypeConversion.GetLine(Title);
    
    Title = ConvertHeader(Title);    
    MainStructure.Insert("title", Title["title"]);
    
EndProcedure

Procedure AddDatabaseDescription(Val Description, MainStructure)
    
    OPI_TypeConversion.GetLine(Description);
    
    Title = ConvertHeader(Description);    
    MainStructure.Insert("description", Title["title"]);
    
EndProcedure

Procedure AddDatabaseProperties(Val Properties, MainStructure)
    
    If Properties.Count() = 0 Then
       MainStructure.Insert("properties", New Structure);
       Return;      
    EndIf;
    
    ParameterMap = New Map;
    
    For Each Property In Properties Do
        
        If TypeOf(Property.Value) = Type("String") Then
            
           ParameterMap.Insert(Property.Key, New Structure(Property.Value, New Structure));
           
        ElsIf TypeOf(Property.Value) = Type("Structure") 
            Or TypeOf(Property.Value) = Type("Map") Then
                
            ValueSelection = FormSelectionValues(Property.Value);
            ParameterMap.Insert(Property.Key, New Structure("select", ValueSelection));
            
        Else
            
            ParameterMap.Insert(Property.Key, Property.Value);
            
        EndIf;
        
    EndDo;
    
    MainStructure.Insert("properties", ParameterMap);
    
EndProcedure

Function FormSelectionValues(Val VariantStructure)
    
    OptionArray = New Array;
    
    For Each Option In VariantStructure Do
        
        OptionMap = New Map;
        OptionMap.Insert("name" , Option.Key);
        OptionMap.Insert("color", Option.Value);
        
        OptionArray.Add(OptionMap);
                
    EndDo;
    
    Return New Structure("options", OptionArray);
        
EndFunction

Function FillDataBySchema(Val Scheme, Val Data, Val Token, Val ThisIsBase = True)
    
    If ThisIsBase Then
        SchemaData = GetDatabase(Token, Scheme);
    Else
        SchemaData = GetPage(Token, Scheme);    
    EndIf;
    
    BaseFields = SchemaData["properties"];
    Properties = New Map;
    
    If ValueIsFilled(BaseFields) Then
        
        For Each Field In BaseFields Do
            
            FieldData = Field.Value;
            FieldType = FieldData["type"];
            
            FillableData = Data.Get(Field.Key);
            
            If FillableData = Undefined Then
                Continue;
            EndIf;
            
            ConvertedData = ConvertValueByType(FieldType, FillableData);
            
            If ConvertedData = Undefined Then
                Continue;
            EndIf;
            
            Properties.Insert(FieldData["id"], ConvertedData); 
            
        EndDo;
        
    EndIf;

    Return Properties;

EndFunction

Procedure RemoveExtraBlockFields(Val Block)
    
    ExtraArray = New Array;
    ExtraArray.Add("request_id");
    ExtraArray.Add("archived");
    ExtraArray.Add("created_by");
    ExtraArray.Add("last_edited_time");
    ExtraArray.Add("created_time");
    ExtraArray.Add("has_children");
    ExtraArray.Add("parrent");
    ExtraArray.Add("last_edited_by");
    ExtraArray.Add("id");
    
    For Each Field In ExtraArray Do
        
        If Not Block.Get(Field) = Undefined Then
            Block.Delete(Field);
        EndIf;
        
    EndDo;
    
EndProcedure

#Region TypeConversion

Function ConvertValueByType(Val Type, Val Value)
    
    If Type = "title" Then
        Return ConvertHeader(Value);
    ElsIf Type = "rich_text" Then
        Return ConvertText(Value);
    ElsIf Type = "number" Then
        Return ConvertNumber(Value);
    ElsIf Type = "select" Then
        Return ConvertSelectionOption(Value);
    ElsIf Type = "multi_select" Then
        Return ConvertMultipleChoice(Value);
    ElsIf Type = "status" Then
        Return ConvertStatus(Value);
    ElsIf Type = "date" Then
        Return ConvertDate(Value);
    ElsIf Type = "relation" Then
        Return ConvertLink(Value);
    ElsIf Type = "people" Then
        Return ConvertUsers(Value);
    ElsIf Type = "files" Then
        Return ConvertFiles(Value);
    ElsIf Type = "checkbox" Then
        Return ConvertBoolean(Value);
    ElsIf Type = "url" Then
        Return ConvertURL(Value);
    ElsIf Type = "email" Then
        Return ConvertEmail(Value);
    ElsIf Type = "phone_number" Then
        Return ConvertPhone(Value);
    Else 
        Return Undefined;
    EndIf;
    
EndFunction

Function ConvertHeader(Val Title) 
    
    DataStructure = New Structure;
    TextStructure = New Structure;
    DataArray = New Array;
    
    TextStructure.Insert("content", Title);
    TextStructure.Insert("link" , Undefined);
    
    DataStructure.Insert("type", "text");
    DataStructure.Insert("text", TextStructure);
       
    DataArray.Add(DataStructure);

    Return New Structure("title", DataArray);
    
EndFunction

Function ConvertText(Val Text)
    
    TextArray = New Array;
    TextStructure = New Structure;
    
    TextStructure.Insert("type", "text");
    TextStructure.Insert("text", New Structure("content", Text));
    
    TextArray.Add(TextStructure);
    
    Return New Structure("rich_text", TextArray);
    
EndFunction

Function ConvertNumber(Val Number)
    Return New Structure("number", Number);
EndFunction

Function ConvertSelectionOption(Val Option)
    
    ChoiceStructure = New Structure;
    ChoiceStructure.Insert("select", New Structure("name", Option));
    
    Return ChoiceStructure;
    
EndFunction

Function ConvertStatus(Val Status)
    
    StatusStructure = New Structure;
    StatusStructure.Insert("status", New Structure("name", Status));
    
    Return StatusStructure;
    
EndFunction

Function ConvertMultipleChoice(Val OptionArray)
    
    ChoiceOptionArray = New Array;
    
    For Each Option In OptionArray Do
        ChoiceOptionArray.Add(New Structure("name", Option));
    EndDo;
    
    Return New Structure("multi_select", ChoiceOptionArray);
    
EndFunction

Function ConvertDate(Val Date)
    
    DateStructure = New Structure;
    
    If Date = BegOfDay(Date) Then
        DateFormat = "DF=yyyy-MM-dd";
    Else
        DateFormat = "ISO8601Datetime";
    EndIf;
    
    Date = Format(Date, DateFormat);
    DateStructure.Insert("start", Date);
    
    Return New Structure("date", DateStructure);
    
EndFunction

Function ConvertLink(Val Identifier) 
    
    LinkArray = New Array;
    LinkArray.Add(New Structure("id", Identifier));
    
    Return New Structure("relation", LinkArray);
    
EndFunction

Function ConvertUsers(Val IDArray)
    
    If Not TypeOf(IDArray) = Type("Array") Then
        ArrayID_ = New Array;
        ArrayID_.Add(IDArray);
        IDArray = ArrayID_;
    EndIf;
    
    ArrayOfUsers = New Array;
    
    For Each Identifier In IDArray Do
        
        UserStructure = New Structure;
        UserStructure.Insert("object", "user");
        UserStructure.Insert("id" , Identifier);
        ArrayOfUsers.Add(UserStructure);
        
    EndDo;
    
    Return New Structure("people", ArrayOfUsers);
    
EndFunction

Function ConvertFiles(Val FileMapping)
        
    ArrayOfFiles = New Array;
    
    For Each File In FileMapping Do
        
        FileStructure = New Structure;
        FileStructure.Insert("type" , "external");
        FileStructure.Insert("name" , File.Key);
        FileStructure.Insert("external", New Structure("url", File.Value));
        
        ArrayOfFiles.Add(FileStructure);
        
    EndDo;

    Return New Structure("files", ArrayOfFiles);
    
EndFunction

Function ConvertBoolean(Val Boolean)
    Return New Structure("checkbox", Boolean);
EndFunction

Function ConvertURL(Val URL)
    Return New Structure("url", URL);
EndFunction

Function ConvertEmail(Val Email)
    Return New Structure("email", Email);
EndFunction

Function ConvertPhone(Val Phone)
    Return New Structure("phone_number", Phone);
EndFunction

#EndRegion

#EndRegion
